feat: add support for validating asar archives on macOS (#30667)

* feat: add support for validating asar archives on macOS

* chore: fix lint

* chore: update as per feedback

* feat: switch implementation to asar integrity hash checks

* feat: make ranged requests work with the asar file validator DataSourceFilter

* chore: fix lint

* chore: fix missing log include on non-darwin

* fix: do not pull block size out of missing optional

* fix: match ValidateOrDie symbol on non-darwin

* chore: fix up asar specs by repacking archives

* fix: maintain integrity chain, do not load file integrity if header integrity was not loaded

* debug test

* Update node-spec.ts

* fix: initialize header_validated_

* chore: update PR per feedback

* chore: update per feedback

* build: use final asar module

* Update fuses.json5
This commit is contained in:
Samuel Attard 2021-09-09 14:49:01 -07:00 committed by GitHub
parent fcad531f2e
commit 57d088517c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 705 additions and 43 deletions

View file

@ -1,6 +1,7 @@
import { Buffer } from 'buffer';
import * as path from 'path';
import * as util from 'util';
import type * as Crypto from 'crypto';
const asar = process._linkedBinding('electron_common_asar');
@ -194,6 +195,20 @@ const overrideAPI = function (module: Record<string, any>, name: string, pathArg
}
};
let crypto: typeof Crypto;
function validateBufferIntegrity (buffer: Buffer, integrity: NodeJS.AsarFileInfo['integrity']) {
if (!integrity) return;
// Delay load crypto to improve app boot performance
// when integrity protection is not enabled
crypto = crypto || require('crypto');
const actual = crypto.createHash(integrity.algorithm).update(buffer).digest('hex');
if (actual !== integrity.hash) {
console.error(`ASAR Integrity Violation: got a hash mismatch (${actual} vs ${integrity.hash})`);
process.exit(1);
}
}
const makePromiseFunction = function (orig: Function, pathArgumentIndex: number) {
return function (this: any, ...args: any[]) {
const pathArgument = args[pathArgumentIndex];
@ -531,7 +546,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
}
const buffer = Buffer.alloc(info.size);
const fd = archive.getFd();
const fd = archive.getFdAndValidateIntegrityLater();
if (!(fd >= 0)) {
const error = createError(AsarError.NOT_FOUND, { asarPath, filePath });
nextTick(callback, [error]);
@ -540,6 +555,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
logASARAccess(asarPath, filePath, info.offset);
fs.read(fd, buffer, 0, info.size, info.offset, (error: Error) => {
validateBufferIntegrity(buffer, info.integrity);
callback(error, encoding ? buffer.toString(encoding) : buffer);
});
}
@ -595,11 +611,12 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
const { encoding } = options;
const buffer = Buffer.alloc(info.size);
const fd = archive.getFd();
const fd = archive.getFdAndValidateIntegrityLater();
if (!(fd >= 0)) throw createError(AsarError.NOT_FOUND, { asarPath, filePath });
logASARAccess(asarPath, filePath, info.offset);
fs.readSync(fd, buffer, 0, info.size, info.offset);
validateBufferIntegrity(buffer, info.integrity);
return (encoding) ? buffer.toString(encoding) : buffer;
};
@ -713,11 +730,12 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
}
const buffer = Buffer.alloc(info.size);
const fd = archive.getFd();
const fd = archive.getFdAndValidateIntegrityLater();
if (!(fd >= 0)) return [];
logASARAccess(asarPath, filePath, info.offset);
fs.readSync(fd, buffer, 0, info.size, info.offset);
validateBufferIntegrity(buffer, info.integrity);
const str = buffer.toString('utf8');
return [str, str.length > 0];
};