Skip to content

Commit a4badef

Browse files
authored
Skip safe atomic swap if update has custom update security policy (#2593)
Also emit a warning when checking if the updater is configured correctly.
1 parent 3a97345 commit a4badef

File tree

4 files changed

+42
-8
lines changed

4 files changed

+42
-8
lines changed

Autoupdate/SUPlainInstaller.m

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -344,26 +344,42 @@ - (BOOL)performInitialInstallation:(NSError * __autoreleasing *)error
344344

345345
SUFileManager *fileManager = [[SUFileManager alloc] init];
346346

347+
BOOL updateHasCustomUpdateSecurityPolicy = NO;
347348
if (@available(macOS 13.0, *)) {
349+
// If the new update is notarized / developer ID code signed and Autoupdate is not signed with the same Team ID as the new update,
350+
// then we may run into Privacy & Security prompt issues from the OS
351+
// which think we are modifying the update (but we're not)
352+
// To avoid these, we skip the gatekeeper scan and skip performing an atomic swap during install
353+
// If the update has a custom update security policy, the same team ID policy may not apply,
354+
// so in that case we will also skip performing an atomic swap
355+
348356
NSURL *mainExecutableURL = NSBundle.mainBundle.executableURL;
349357
if (mainExecutableURL == nil) {
350358
// This shouldn't happen
351359
_canPerformSafeAtomicSwap = NO;
352360
} else {
353-
NSString *installerTeamIdentifier = [SUCodeSigningVerifier teamIdentifierAtURL:mainExecutableURL];
354-
NSString *bundleTeamIdentifier = [SUCodeSigningVerifier teamIdentifierAtURL:bundle.bundleURL];
355-
356-
// If the new update is code signed and Autoupdate is not signed with the same Team ID as the new update,
357-
// then we may run into Privacy & Security prompt issues from the OS
358-
// To avoid these, we skip the gatekeeper scan and skip performing an atomic swap during install
359-
_canPerformSafeAtomicSwap = (bundleTeamIdentifier == nil || (installerTeamIdentifier != nil && [installerTeamIdentifier isEqualToString:bundleTeamIdentifier]));
361+
updateHasCustomUpdateSecurityPolicy = updateHost.hasUpdateSecurityPolicy;
362+
if (updateHasCustomUpdateSecurityPolicy) {
363+
// We don't handle working around a custom update security policy
364+
_canPerformSafeAtomicSwap = NO;
365+
} else {
366+
NSString *installerTeamIdentifier = [SUCodeSigningVerifier teamIdentifierAtURL:mainExecutableURL];
367+
NSString *bundleTeamIdentifier = [SUCodeSigningVerifier teamIdentifierAtURL:bundle.bundleURL];
368+
369+
// If bundleTeamIdentifier is nil, then the update isn't code signed so atomic swap is okay
370+
_canPerformSafeAtomicSwap = (bundleTeamIdentifier == nil || (installerTeamIdentifier != nil && [installerTeamIdentifier isEqualToString:bundleTeamIdentifier]));
371+
}
360372
}
361373
} else {
362374
_canPerformSafeAtomicSwap = YES;
363375
}
364376

365377
if (!_canPerformSafeAtomicSwap) {
366-
SULog(SULogLevelDefault, @"Skipping atomic rename/swap and gatekeeper scan because Autoupdate is not signed with same identity as the new update %@", bundle.bundleURL.lastPathComponent);
378+
if (updateHasCustomUpdateSecurityPolicy) {
379+
SULog(SULogLevelDefault, @"Skipping atomic rename/swap and gatekeeper scan because new update %@ has a custom NSUpdateSecurityPolicy", bundle.bundleURL.lastPathComponent);
380+
} else {
381+
SULog(SULogLevelDefault, @"Skipping atomic rename/swap and gatekeeper scan because Autoupdate is not signed with same identity as the new update %@", bundle.bundleURL.lastPathComponent);
382+
}
367383
}
368384

369385
_newAndOldBundlesOnSameVolume = [fileManager itemAtURL:bundle.bundleURL isOnSameVolumeItemAsURL:_host.bundle.bundleURL];

Sparkle/SPUUpdater.m

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ @implementation SPUUpdater
7676
BOOL _showingPermissionRequest;
7777
BOOL _loggedATSWarning;
7878
BOOL _loggedNoSecureKeyWarning;
79+
BOOL _loggedUpdateSecurityPolicyWarning;
7980
BOOL _updatingMainBundle;
8081
}
8182

@@ -361,6 +362,14 @@ - (BOOL)checkIfConfiguredProperlyAndRequireFeedURL:(BOOL)requireFeedURL validate
361362
}
362363
}
363364

365+
if (_updatingMainBundle) {
366+
if (!_loggedUpdateSecurityPolicyWarning && mainBundleHost.hasUpdateSecurityPolicy) {
367+
SULog(SULogLevelDefault, @"Warning: %@ has a custom NSUpdateSecurityPolicy in its Info.plist. This may cause issues when installing updates. Please consider removing this key for your builds using Sparkle if you do not really require a custom update security policy.", hostName);
368+
369+
_loggedUpdateSecurityPolicyWarning = YES;
370+
}
371+
}
372+
364373
return YES;
365374
}
366375

Sparkle/SUHost.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ SPU_OBJC_DIRECT_MEMBERS
3333
@property (getter=isRunningTranslocated, nonatomic, readonly) BOOL runningTranslocated;
3434
@property (readonly, nonatomic, copy, nullable) NSString *publicDSAKeyFileKey;
3535

36+
@property (nonatomic, readonly) BOOL hasUpdateSecurityPolicy;
37+
3638
- (nullable id)objectForInfoDictionaryKey:(NSString *)key;
3739
- (BOOL)boolForInfoDictionaryKey:(NSString *)key;
3840
- (nullable id)objectForUserDefaultsKey:(NSString *)defaultName;

Sparkle/SUHost.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,13 @@ - (NSString *_Nullable)publicDSAKey SPU_OBJC_DIRECT
161161
return key;
162162
}
163163

164+
- (BOOL)hasUpdateSecurityPolicy
165+
{
166+
NSDictionary<NSString *, id> *updateSecurityPolicy = [self objectForInfoDictionaryKey:@"NSUpdateSecurityPolicy"];
167+
168+
return (updateSecurityPolicy != nil);
169+
}
170+
164171
- (SUPublicKeys *)publicKeys
165172
{
166173
return [[SUPublicKeys alloc] initWithEd:[self publicEDKey]

0 commit comments

Comments
 (0)