Don't pass installer/agent path to launcher XPC service

The InstallerLauncher code now tries to locate these tools inside Contents/MacOS/ or inside the Resources directory. The installer launcher XPC service now has a copy of the tools included in its bundle.

This may seem like wasting space but it enables eg, the XPC service not trusting a sandboxed application. A developer could always remove the tools from the Sparkle framework if necessary (we can't because the XPC services are optional..)

Using the XPC service allows the developer to sign the installer executable (Autoupdate.app). The agent app can be moved and signed into the MacOS executables directory as well. We don't do that automatically because Xcode's tooling doesn't play nice with attempting that, although that could be looked at later.
This commit is contained in:
Zorg
2016-07-27 19:33:06 -04:00
parent 8ab7bd6edb
commit 3be36d8f22
6 changed files with 169 additions and 68 deletions
-17
View File
@@ -1,17 +0,0 @@
//
// SUAuthorizationReply.h
// Sparkle
//
// Created by Mayur Pawashe on 7/18/16.
// Copyright © 2016 Sparkle Project. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, SUAuthorizationReply)
{
SUAuthorizationReplySuccess = 0,
SUAuthorizationReplyCanceled = 1,
SUAuthorizationReplyFailure = 2,
SUAuthorizationReplyAuthorizeLater = 3
};
+100 -30
View File
@@ -12,6 +12,7 @@
#import "SUMessageTypes.h"
#import "SUSystemAuthorization.h"
#import "SUBundleIcon.h"
#import "SULocalCacheDirectory.h"
#import <ServiceManagement/ServiceManagement.h>
#ifdef _APPKITDEFINES_H
@@ -90,7 +91,7 @@
return (submittedJob == true);
}
- (SUAuthorizationReply)submitInstallerAtPath:(NSString *)installerPath withHostBundle:(NSBundle *)hostBundle authorizationPrompt:(NSString *)authorizationPrompt inSystemDomain:(BOOL)systemDomain
- (SUInstallerLauncherStatus)submitInstallerAtPath:(NSString *)installerPath withHostBundle:(NSBundle *)hostBundle authorizationPrompt:(NSString *)authorizationPrompt inSystemDomain:(BOOL)systemDomain
{
SUFileManager *fileManager = [SUFileManager defaultManager];
@@ -249,48 +250,117 @@
AuthorizationFree(auth, kAuthorizationFlagDefaults);
}
SUAuthorizationReply reply;
SUInstallerLauncherStatus status;
if (submittedJob == true) {
reply = SUAuthorizationReplySuccess;
status = SUInstallerLauncherSuccess;
} else if (canceledAuthorization) {
reply = SUAuthorizationReplyCanceled;
status = SUInstallerLauncherCanceled;
} else {
reply = SUAuthorizationReplyFailure;
status = SUInstallerLauncherFailure;
}
return reply;
return status;
}
- (void)launchInstallerAtPath:(NSString *)installerPath progressToolPath:(NSString *)progressToolPath withHostBundlePath:(NSString *)hostBundlePath authorizationPrompt:(NSString *)authorizationPrompt installationType:(NSString *)installationType allowingDriverInteraction:(BOOL)allowingDriverInteraction allowingUpdaterInteraction:(BOOL)allowingUpdaterInteraction completion:(void (^)(SUAuthorizationReply))completionHandler
// First we check if the tool is in an auxiliary directory. If that fails, we then check if it is in a resources directory
- (NSString *)pathForBundledTool:(NSString *)toolName extension:(NSString *)extension inBundle:(NSBundle *)bundle
{
NSString *resultPath = nil;
// If the path extension is empty, we don't want to add a "." at the end
NSString *pathWithExtension = (extension.length > 0) ? [toolName stringByAppendingPathExtension:extension] : toolName;
assert(pathWithExtension != nil);
NSString *auxiliaryPath = [bundle pathForAuxiliaryExecutable:pathWithExtension];
if (auxiliaryPath == nil || ![[NSFileManager defaultManager] fileExistsAtPath:auxiliaryPath]) {
resultPath = [bundle pathForResource:toolName ofType:extension];
} else {
resultPath = auxiliaryPath;
}
return resultPath;
}
- (void)launchInstallerWithHostBundlePath:(NSString *)hostBundlePath authorizationPrompt:(NSString *)authorizationPrompt installationType:(NSString *)installationType allowingDriverInteraction:(BOOL)allowingDriverInteraction allowingUpdaterInteraction:(BOOL)allowingUpdaterInteraction completion:(void (^)(SUInstallerLauncherStatus))completionHandler
{
dispatch_async(dispatch_get_main_queue(), ^{
NSBundle *hostBundle = [NSBundle bundleWithPath:hostBundlePath];
BOOL needsSystemAuthorization = SUNeedsSystemAuthorizationAccess(hostBundlePath, installationType);
// if we need to use the system domain and we aren't allowed interaction, then try sometime later when interaction is allowed
if (needsSystemAuthorization && !allowingUpdaterInteraction) {
completionHandler(SUAuthorizationReplyFailure);
} else if (needsSystemAuthorization && !allowingDriverInteraction) {
completionHandler(SUAuthorizationReplyAuthorizeLater);
SULog(@"Updater is not allowing interaction with the launcer.");
completionHandler(SUInstallerLauncherFailure);
return;
}
// if we need to use the system domain and we aren't allowed interaction, then try sometime later when interaction is allowed
if (needsSystemAuthorization && !allowingDriverInteraction) {
completionHandler(SUInstallerLauncherAuthorizeLater);
return;
}
NSString *hostBundleIdentifier = hostBundle.bundleIdentifier;
assert(hostBundleIdentifier != nil);
// We could be inside the InstallerLauncher XPC bundle or in the Sparkle.framework bundle if no XPC service is used
NSBundle *ourBundle = [NSBundle bundleForClass:[self class]];
// Note we do not have to copy this tool out of the bundle it's in because it's a utility with no dependencies.
// Furthermore, we can keep the tool at a place that may not necessarily be writable.
NSString *installerPath = [self pathForBundledTool:@""SPARKLE_RELAUNCH_TOOL_NAME extension:@"" inBundle:ourBundle];
if (installerPath == nil) {
SULog(@"Error: Cannot submit installer because the installer could not be located");
completionHandler(SUInstallerLauncherFailure);
return;
}
// We do however have to copy the progress tool app somewhere safe due to its external depedencies
NSString *progressToolResourcePath = [self pathForBundledTool:@""SPARKLE_INSTALLER_PROGRESS_TOOL_NAME extension:@"app" inBundle:ourBundle];
if (progressToolResourcePath == nil) {
SULog(@"Error: Cannot submit progress tool because the progress tool could not be located");
completionHandler(SUInstallerLauncherFailure);
return;
}
NSString *cachePath = [SULocalCacheDirectory cachePathForBundleIdentifier:hostBundleIdentifier];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *createCacheError = nil;
// Do not remove the cache directory if it already exists since it may be containing downloaded files
if (![fileManager createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:&createCacheError]) {
SULog(@"Failed to create cache directory for progress tool: %@", createCacheError);
completionHandler(SUInstallerLauncherFailure);
return;
}
NSString *progressToolPath = [cachePath stringByAppendingPathComponent:@""SPARKLE_INSTALLER_PROGRESS_TOOL_NAME@".app"];
if ([fileManager fileExistsAtPath:progressToolPath]) {
[fileManager removeItemAtPath:progressToolPath error:NULL];
}
NSError *copyError = nil;
// SUFileManager is more reliable for copying files around
if (![[SUFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:progressToolResourcePath] toURL:[NSURL fileURLWithPath:progressToolPath] error:&copyError]) {
SULog(@"Failed to copy progress tool to cache: %@", copyError);
completionHandler(SUInstallerLauncherFailure);
return;
}
SUInstallerLauncherStatus installerStatus = [self submitInstallerAtPath:installerPath withHostBundle:hostBundle authorizationPrompt:authorizationPrompt inSystemDomain:needsSystemAuthorization];
BOOL submittedProgressTool = NO;
if (installerStatus == SUInstallerLauncherSuccess) {
submittedProgressTool = [self submitProgressToolAtPath:progressToolPath withHostBundle:hostBundle inSystemDomainForInstaller:needsSystemAuthorization];
if (!submittedProgressTool) {
SULog(@"Failed to submit progress tool job");
}
} else if (installerStatus == SUInstallerLauncherFailure) {
SULog(@"Failed to submit installer job");
}
if (installerStatus == SUInstallerLauncherCanceled) {
completionHandler(installerStatus);
} else {
SUAuthorizationReply installerReply = [self submitInstallerAtPath:installerPath withHostBundle:hostBundle authorizationPrompt:authorizationPrompt inSystemDomain:needsSystemAuthorization];
BOOL submittedProgressTool = NO;
if (installerReply == SUAuthorizationReplySuccess) {
submittedProgressTool = [self submitProgressToolAtPath:progressToolPath withHostBundle:hostBundle inSystemDomainForInstaller:needsSystemAuthorization];
if (!submittedProgressTool) {
SULog(@"Failed to submit progress tool job");
}
} else if (installerReply == SUAuthorizationReplyFailure) {
SULog(@"Failed to submit installer job");
}
if (installerReply == SUAuthorizationReplyCanceled) {
completionHandler(installerReply);
} else {
completionHandler(submittedProgressTool ? SUAuthorizationReplySuccess : SUAuthorizationReplyFailure);
}
completionHandler(submittedProgressTool ? SUInstallerLauncherSuccess : SUInstallerLauncherFailure);
}
});
}
@@ -7,10 +7,10 @@
//
#import <Foundation/Foundation.h>
#import "SUAuthorizationReply.h"
#import "SUInstallerLauncherStatus.h"
@protocol SUInstallerLauncherProtocol
- (void)launchInstallerAtPath:(NSString *)installerPath progressToolPath:(NSString *)progressToolPath withHostBundlePath:(NSString *)hostBundlePath authorizationPrompt:(NSString *)authorizationPrompt installationType:(NSString *)installationType allowingDriverInteraction:(BOOL)allowingDriverInteraction allowingUpdaterInteraction:(BOOL)allowingUpdaterInteraction completion:(void (^)(SUAuthorizationReply))completionHandler;
- (void)launchInstallerWithHostBundlePath:(NSString *)hostBundlePath authorizationPrompt:(NSString *)authorizationPrompt installationType:(NSString *)installationType allowingDriverInteraction:(BOOL)allowingDriverInteraction allowingUpdaterInteraction:(BOOL)allowingUpdaterInteraction completion:(void (^)(SUInstallerLauncherStatus))completionHandler;
@end
@@ -0,0 +1,17 @@
//
// SUInstallerLauncherStatus.h
// Sparkle
//
// Created by Mayur Pawashe on 7/18/16.
// Copyright © 2016 Sparkle Project. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, SUInstallerLauncherStatus)
{
SUInstallerLauncherSuccess = 0,
SUInstallerLauncherCanceled = 1,
SUInstallerLauncherAuthorizeLater = 3,
SUInstallerLauncherFailure = 4
};
+45 -2
View File
@@ -118,6 +118,9 @@
721CF1AA1AD7647000D9AC09 /* libxar.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D1AF5890FD7678C0065DB48 /* libxar.1.dylib */; };
721CF1AB1AD764EB00D9AC09 /* libbz2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D06E8FB0FD68D61005AE3F6 /* libbz2.dylib */; };
721D8A611D48413B0032E472 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61B5F8F609C4CEB300B25A18 /* Security.framework */; };
721D8A721D4950B80032E472 /* Autoupdate in Copy Tools */ = {isa = PBXBuildFile; fileRef = 72B398D21D3D879300EE297F /* Autoupdate */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
721D8A761D4952430032E472 /* Updater.app in Resources */ = {isa = PBXBuildFile; fileRef = 721C24451CB753E6005440CB /* Updater.app */; };
721D8A7B1D4963190032E472 /* SULocalCacheDirectory.m in Sources */ = {isa = PBXBuildFile; fileRef = 721BC20D1D1CDE55002BC71E /* SULocalCacheDirectory.m */; };
7223E7631AD1AEFF008E3161 /* sais.c in Sources */ = {isa = PBXBuildFile; fileRef = 7223E7611AD1AEFF008E3161 /* sais.c */; };
7229E1B61C97C91100CB50D0 /* SUUpdateDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 7229E1B51C97C91100CB50D0 /* SUUpdateDriver.h */; };
7229E1B91C97CC4D00CB50D0 /* SUScheduledUpdateDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 7229E1B71C97CC4D00CB50D0 /* SUScheduledUpdateDriver.h */; };
@@ -393,6 +396,20 @@
remoteGlobalIDString = 8DC2EF4F0486A6940098B216;
remoteInfo = Sparkle;
};
721D8A771D4952B60032E472 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0867D690FE84028FC02AAC07 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 72B398D11D3D879300EE297F;
remoteInfo = Autoupdate;
};
721D8A791D4952B60032E472 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0867D690FE84028FC02AAC07 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 721C24441CB753E6005440CB;
remoteInfo = "Installer Progress";
};
724BB3821D3203A9005D534A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0867D690FE84028FC02AAC07 /* Project object */;
@@ -513,6 +530,17 @@
name = "Copy Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
721D8A711D4950910032E472 /* Copy Tools */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 6;
files = (
721D8A721D4950B80032E472 /* Autoupdate in Copy Tools */,
);
name = "Copy Tools";
runOnlyForDeploymentPostprocessing = 0;
};
726E4A261C86C88F00C57C6A /* Embed XPC Services */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -760,7 +788,7 @@
7214B8851D45AD9A00CB5CED /* SUInstallationType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SUInstallationType.h; sourceTree = "<group>"; };
72162B051C82C8B30013C1C5 /* SUParameterAssert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUParameterAssert.h; sourceTree = "<group>"; };
72162B071C82C9600013C1C5 /* SULocalizations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SULocalizations.h; sourceTree = "<group>"; };
721652671D3C8FED00FD13D8 /* SUAuthorizationReply.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SUAuthorizationReply.h; path = InstallerLauncher/SUAuthorizationReply.h; sourceTree = SOURCE_ROOT; };
721652671D3C8FED00FD13D8 /* SUInstallerLauncherStatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SUInstallerLauncherStatus.h; path = InstallerLauncher/SUInstallerLauncherStatus.h; sourceTree = SOURCE_ROOT; };
721BC2061D17A532002BC71E /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
721BC2081D17A553002BC71E /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
721BC20A1D17A5AD002BC71E /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
@@ -1625,7 +1653,7 @@
726E07AE1CAF08D6001A286B /* InstallerLauncher */ = {
isa = PBXGroup;
children = (
721652671D3C8FED00FD13D8 /* SUAuthorizationReply.h */,
721652671D3C8FED00FD13D8 /* SUInstallerLauncherStatus.h */,
726E07AF1CAF08D6001A286B /* SUInstallerLauncherProtocol.h */,
726E07B01CAF08D6001A286B /* SUInstallerLauncher.h */,
726E07B11CAF08D6001A286B /* SUInstallerLauncher.m */,
@@ -2023,10 +2051,13 @@
726E07A91CAF08D6001A286B /* Sources */,
726E07AA1CAF08D6001A286B /* Frameworks */,
726E07AB1CAF08D6001A286B /* Resources */,
721D8A711D4950910032E472 /* Copy Tools */,
);
buildRules = (
);
dependencies = (
721D8A781D4952B60032E472 /* PBXTargetDependency */,
721D8A7A1D4952B60032E472 /* PBXTargetDependency */,
);
name = SparkleInstallerLauncher;
productName = InstallerLauncher;
@@ -2365,6 +2396,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
721D8A761D4952430032E472 /* Updater.app in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2612,6 +2644,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
721D8A7B1D4963190032E472 /* SULocalCacheDirectory.m in Sources */,
7214B8841D456C9500CB5CED /* SUBundleIcon.m in Sources */,
7267E5E11D3D901600D1BF90 /* SUMessageTypes.m in Sources */,
724BB3AF1D34828A005D534A /* SUSystemAuthorization.m in Sources */,
@@ -2812,6 +2845,16 @@
target = 8DC2EF4F0486A6940098B216 /* Sparkle */;
targetProxy = 61FA528C0E2D9EB200EF58AD /* PBXContainerItemProxy */;
};
721D8A781D4952B60032E472 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 72B398D11D3D879300EE297F /* Autoupdate */;
targetProxy = 721D8A771D4952B60032E472 /* PBXContainerItemProxy */;
};
721D8A7A1D4952B60032E472 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 721C24441CB753E6005440CB /* Installer Progress */;
targetProxy = 721D8A791D4952B60032E472 /* PBXContainerItemProxy */;
};
724BB3831D3203A9005D534A /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 724BB36B1D31D0B7005D534A /* SparkleInstallerConnection */;
+5 -17
View File
@@ -369,18 +369,6 @@
- (void)launchAutoUpdateSilently:(BOOL)silently completion:(void (^)(NSError *_Nullable))completionHandler
{
// Copy the progress tool into a temporary directory so we can get to it as the new version is being installed.
NSError *progressToolError = nil;
NSString *progressToolPath = [self copyPathToCacheDirectory:[self.sparkleBundle pathForResource:@""SPARKLE_INSTALLER_PROGRESS_TOOL_NAME ofType:@"app"] error:&progressToolError];
if (progressToolPath == nil) {
completionHandler(progressToolError);
return;
}
// Grab the installer path. Note we do not have to copy this tool out of where it may be located right now since it's a utility with no dependencies.
// Furthermore, we can keep the tool at a place that may not necessarily be writable.
NSString *relaunchToolPath = [self.sparkleBundle pathForResource:@""SPARKLE_RELAUNCH_TOOL_NAME ofType:@""];
id<SUInstallerLauncherProtocol> installerLauncher = nil;
__block BOOL retrievedLaunchStatus = NO;
NSXPCConnection *launcherConnection = nil;
@@ -444,23 +432,23 @@
// The installer launcher could be in a XPC service, so we don't want to do localization in there
NSString *authorizationPrompt = [NSString stringWithFormat:SULocalizedString(@"%1$@ wants to update.", nil), self.host.name];
[installerLauncher launchInstallerAtPath:relaunchToolPath progressToolPath:progressToolPath withHostBundlePath:hostBundlePath authorizationPrompt:authorizationPrompt installationType:installationType allowingDriverInteraction:driverAllowsInteraction allowingUpdaterInteraction:updaterAllowsInteraction completion:^(SUAuthorizationReply result) {
[installerLauncher launchInstallerWithHostBundlePath:hostBundlePath authorizationPrompt:authorizationPrompt installationType:installationType allowingDriverInteraction:driverAllowsInteraction allowingUpdaterInteraction:updaterAllowsInteraction completion:^(SUInstallerLauncherStatus result) {
dispatch_async(dispatch_get_main_queue(), ^{
retrievedLaunchStatus = YES;
[launcherConnection invalidate];
switch (result) {
case SUAuthorizationReplyFailure:
case SUInstallerLauncherFailure:
SULog(@"Error: Failed to gain authorization required to update target");
completionHandler([NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:@{ NSLocalizedDescriptionKey:SULocalizedString(@"An error occurred while launching the installer. Please try again later.", nil) }]);
break;
case SUAuthorizationReplyCanceled:
case SUInstallerLauncherCanceled:
completionHandler([NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationCanceledError userInfo:nil]);
break;
case SUAuthorizationReplyAuthorizeLater:
case SUInstallerLauncherAuthorizeLater:
completionHandler([NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationAuthorizeLaterError userInfo:nil]);
break;
case SUAuthorizationReplySuccess:
case SUInstallerLauncherSuccess:
[self setUpConnection];
completionHandler(nil);
break;