From 007e9aee6160cf132645b32ad127d6562a2881b9 Mon Sep 17 00:00:00 2001 From: Zorg Date: Sat, 15 Jun 2024 18:13:36 -0700 Subject: [PATCH] Randomize the download archive name the installer extracts/executes (#2584) --- Autoupdate/AppInstaller.m | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Autoupdate/AppInstaller.m b/Autoupdate/AppInstaller.m index 9ae3d7226..f7e1d2923 100644 --- a/Autoupdate/AppInstaller.m +++ b/Autoupdate/AppInstaller.m @@ -415,13 +415,41 @@ - (void)handleMessageWithIdentifier:(int32_t)identifier data:(NSData *)data SULog(SULogLevelError, @"Error: bookmark data for update download is stale.. but still continuing."); } - NSString *downloadName = downloadURL.lastPathComponent; - if (downloadName == nil) { + NSString *originalDownloadName = downloadURL.lastPathComponent; + if (originalDownloadName == nil) { [self cleanupAndExitWithStatus:EXIT_FAILURE error:[NSError errorWithDomain:SUSparkleErrorDomain code:SPUInstallerError userInfo:@{ NSLocalizedDescriptionKey: @"Error: Failed to retrieve download name from download URL" }]]; return; } + // Randomize the download name if possible + // This adds better security if there are any vulnerabilities in extracting/executing archives + // which allow writing in unexpected locations. For zip/tar/dmg archives we may also extract them before + // performing signing validation (due to key rotation). + NSString *downloadName; + NSString *randomizedUUIDString = [[NSUUID UUID] UUIDString]; + if (randomizedUUIDString != nil) { + // Find the real path extension of the download name + // We cannot use -[NSString pathExtension] because it may not give us the full path extension + // E.g. for "foo.tar.xz" we need "tar.xz", not "xz" + NSString *downloadPathExtension; + NSRange pathExtensionDelimiterRange = [originalDownloadName rangeOfString:@"."]; + if (pathExtensionDelimiterRange.location == NSNotFound) { + downloadPathExtension = @""; + } else { + downloadPathExtension = [originalDownloadName substringFromIndex:pathExtensionDelimiterRange.location + 1]; + } + + NSString *randomizedDownloadName = [randomizedUUIDString stringByAppendingPathExtension:downloadPathExtension]; + if (randomizedDownloadName != nil) { + downloadName = randomizedDownloadName; + } else { + downloadName = originalDownloadName; + } + } else { + downloadName = originalDownloadName; + } + // Move the download archive to somewhere where probably only we will be touching it // This prevents eg: if a bug exists in the updater that removes files we are trying to install // When this tool is ran as root, we are moving it into a directory that only root will have access to