fix: Squirrel.Mac crash when zip extraction fails (#47300)

* fix: Squirrel.Mac crash when zip extraction process fails to launch

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

* chore: add end-to-end test

Co-authored-by: Niklas Wenzel <dev@nikwen.de>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Niklas Wenzel <dev@nikwen.de>
This commit is contained in:
trop[bot] 2025-05-30 10:46:53 -04:00 committed by GitHub
commit 2a707ffbd8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 81 additions and 0 deletions

View file

@ -7,3 +7,4 @@ fix_abort_installation_attempt_at_the_final_mile_if_the_app_is.patch
feat_add_ability_to_prevent_version_downgrades.patch feat_add_ability_to_prevent_version_downgrades.patch
refactor_use_non-deprecated_nskeyedarchiver_apis.patch refactor_use_non-deprecated_nskeyedarchiver_apis.patch
chore_turn_off_launchapplicationaturl_deprecation_errors_in_squirrel.patch chore_turn_off_launchapplicationaturl_deprecation_errors_in_squirrel.patch
fix_crash_when_process_to_extract_zip_cannot_be_launched.patch

View file

@ -0,0 +1,30 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Niklas Wenzel <dev@nikwen.de>
Date: Tue, 27 May 2025 02:03:54 +0200
Subject: fix: crash when process to extract zip cannot be launched
Fixes https://github.com/electron/electron/issues/47270
diff --git a/Squirrel/SQRLZipArchiver.m b/Squirrel/SQRLZipArchiver.m
index 68f5dac8e553638f41306956df9d38eeda18f8f2..a9cd676df63e19edf9e20473d27b85591c7cb49e 100644
--- a/Squirrel/SQRLZipArchiver.m
+++ b/Squirrel/SQRLZipArchiver.m
@@ -153,7 +153,17 @@ - (RACSignal *)launchWithArguments:(NSArray *)arguments {
setNameWithFormat:@"-launchWithArguments: %@", arguments];
self.dittoTask.arguments = arguments;
- [self.dittoTask launch];
+
+ NSError *launchError = nil;
+
+ if (![self.dittoTask launchAndReturnError:&launchError]) {
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
+ userInfo[NSLocalizedDescriptionKey] = launchError.localizedDescription;
+
+ NSLog(@"Starting ditto task failed with error: %@", launchError.localizedDescription);
+
+ return [RACSignal error:[NSError errorWithDomain:SQRLZipArchiverErrorDomain code:SQRLZipArchiverShellTaskFailed userInfo:userInfo]];
+ }
return signal;
}

View file

@ -42,6 +42,16 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
return cp.spawn(path.resolve(appPath, 'Contents/MacOS/Electron'), args); return cp.spawn(path.resolve(appPath, 'Contents/MacOS/Electron'), args);
}; };
const launchAppSandboxed = (appPath: string, profilePath: string, args: string[] = []) => {
return spawn('/usr/bin/sandbox-exec', [
'-f',
profilePath,
path.resolve(appPath, 'Contents/MacOS/Electron'),
...args,
'--no-sandbox'
]);
};
const getRunningShipIts = async (appPath: string) => { const getRunningShipIts = async (appPath: string) => {
const processes = await psList(); const processes = await psList();
const activeShipIts = processes.filter(p => p.cmd?.includes('Squirrel.framework/Resources/ShipIt com.github.Electron.ShipIt') && p.cmd!.startsWith(appPath)); const activeShipIts = processes.filter(p => p.cmd?.includes('Squirrel.framework/Resources/ShipIt com.github.Electron.ShipIt') && p.cmd!.startsWith(appPath));
@ -740,6 +750,41 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
}); });
}); });
it('should hit the download endpoint when an update is available and fail when the zip extraction process fails to launch', async () => {
await withUpdatableApp({
nextVersion: '2.0.0',
startFixture: 'update',
endFixture: 'update'
}, async (appPath, updateZipPath) => {
server.get('/update-file', (req, res) => {
res.download(updateZipPath);
});
server.get('/update-check', (req, res) => {
res.json({
url: `http://localhost:${port}/update-file`,
name: 'My Release Name',
notes: 'Theses are some release notes innit',
pub_date: (new Date()).toString()
});
});
const launchResult = await launchAppSandboxed(
appPath,
path.resolve(__dirname, 'fixtures/auto-update/sandbox/block-ditto.sb'),
[`http://localhost:${port}/update-check`]
);
logOnError(launchResult, () => {
expect(launchResult).to.have.property('code', 1);
expect(launchResult.out).to.include('Starting ditto task failed with error:');
expect(launchResult.out).to.include('SQRLZipArchiverErrorDomain');
expect(requests).to.have.lengthOf(2);
expect(requests[0]).to.have.property('url', '/update-check');
expect(requests[1]).to.have.property('url', '/update-file');
expect(requests[0].header('user-agent')).to.include('Electron/');
expect(requests[1].header('user-agent')).to.include('Electron/');
});
});
});
it('should hit the download endpoint when an update is available and update successfully when the zip is provided with JSON update mode', async () => { it('should hit the download endpoint when an update is available and update successfully when the zip is provided with JSON update mode', async () => {
await withUpdatableApp({ await withUpdatableApp({
nextVersion: '2.0.0', nextVersion: '2.0.0',

View file

@ -0,0 +1,5 @@
(version 1)
(allow default)
(deny process-exec
(literal "/usr/bin/ditto")
)