From af7fe484a215fc949360df1fba763b4cd3ffda74 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Tue, 22 Sep 2020 15:11:42 -0700 Subject: [PATCH] [AltDaemon] Replaces local socket communication with XPC Allows AltDaemon to be launched on demand + reject any connections not made from AltStore. --- AltDaemon/AltDaemon-Bridging-Header.h | 17 ++ AltDaemon/AppManager.swift | 3 +- ...ndler.swift => DaemonRequestHandler.swift} | 6 +- AltDaemon/LocalConnectionHandler.swift | 108 ------------- AltDaemon/XPCConnectionHandler.swift | 90 +++++++++++ .../com.rileytestut.altdaemon.plist | 9 +- AltStore.xcodeproj/project.pbxproj | 30 ++-- AltStore/Operations/FindServerOperation.swift | 19 ++- AltStore/Server/ServerConnection.swift | 76 +++------ AltStore/Server/ServerManager.swift | 109 +++++++------ Dependencies/AltSign | 2 +- .../Categories/CFNotificationName+AltStore.h | 4 - .../Categories/CFNotificationName+AltStore.m | 4 - Shared/Connections/XPCConnection.swift | 150 ++++++++++++++++++ .../NSXPCConnection+MachServices.swift | 33 ++++ 15 files changed, 419 insertions(+), 241 deletions(-) rename AltDaemon/{RequestHandler.swift => DaemonRequestHandler.swift} (98%) delete mode 100644 AltDaemon/LocalConnectionHandler.swift create mode 100644 AltDaemon/XPCConnectionHandler.swift create mode 100644 Shared/Connections/XPCConnection.swift create mode 100644 Shared/Extensions/NSXPCConnection+MachServices.swift diff --git a/AltDaemon/AltDaemon-Bridging-Header.h b/AltDaemon/AltDaemon-Bridging-Header.h index 21b01a92..94f57276 100644 --- a/AltDaemon/AltDaemon-Bridging-Header.h +++ b/AltDaemon/AltDaemon-Bridging-Header.h @@ -10,6 +10,23 @@ #import "NSError+ALTServerError.h" #import "CFNotificationName+AltStore.h" +// libproc +int proc_pidpath(int pid, void * buffer, uint32_t buffersize); + +// Security.framework +CF_ENUM(uint32_t) { + kSecCSInternalInformation = 1 << 0, + kSecCSSigningInformation = 1 << 1, + kSecCSRequirementInformation = 1 << 2, + kSecCSDynamicInformation = 1 << 3, + kSecCSContentInformation = 1 << 4, + kSecCSSkipResourceDirectory = 1 << 5, + kSecCSCalculateCMSDigest = 1 << 6, +}; + +OSStatus SecStaticCodeCreateWithPath(CFURLRef path, uint32_t flags, void ** __nonnull CF_RETURNS_RETAINED staticCode); +OSStatus SecCodeCopySigningInformation(void *code, uint32_t flags, CFDictionaryRef * __nonnull CF_RETURNS_RETAINED information); + NS_ASSUME_NONNULL_BEGIN @interface AKDevice : NSObject diff --git a/AltDaemon/AppManager.swift b/AltDaemon/AppManager.swift index e2e4bca9..a0408ba2 100644 --- a/AltDaemon/AppManager.swift +++ b/AltDaemon/AppManager.swift @@ -75,7 +75,8 @@ struct AppManager // Remove all inactive profiles (if active profiles are provided), and the previous profiles. for fileURL in profileURLs { - guard let profile = ALTProvisioningProfile(url: fileURL) else { continue } + // Use memory mapping to reduce peak memory usage and stay within limit. + guard let profile = try? ALTProvisioningProfile(url: fileURL, options: [.mappedIfSafe]) else { continue } if installingBundleIDs.contains(profile.bundleIdentifier) || (activeProfiles?.contains(profile.bundleIdentifier) == false && profile.isFreeProvisioningProfile) { diff --git a/AltDaemon/RequestHandler.swift b/AltDaemon/DaemonRequestHandler.swift similarity index 98% rename from AltDaemon/RequestHandler.swift rename to AltDaemon/DaemonRequestHandler.swift index 9c1afb21..48b2d7c7 100644 --- a/AltDaemon/RequestHandler.swift +++ b/AltDaemon/DaemonRequestHandler.swift @@ -1,6 +1,6 @@ // -// ConnectionManager.swift -// AltServer +// DaemonRequestHandler.swift +// AltDaemon // // Created by Riley Testut on 6/1/20. // Copyright © 2019 Riley Testut. All rights reserved. @@ -11,7 +11,7 @@ import Foundation typealias DaemonConnectionManager = ConnectionManager private let connectionManager = ConnectionManager(requestHandler: DaemonRequestHandler(), - connectionHandlers: [LocalConnectionHandler()]) + connectionHandlers: [XPCConnectionHandler()]) extension DaemonConnectionManager { diff --git a/AltDaemon/LocalConnectionHandler.swift b/AltDaemon/LocalConnectionHandler.swift deleted file mode 100644 index 15c4e97b..00000000 --- a/AltDaemon/LocalConnectionHandler.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// LocalConnectionHandler.swift -// AltDaemon -// -// Created by Riley Testut on 6/2/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -import Foundation -import Network - -private let ReceivedLocalServerConnectionRequest: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void = -{ (center, observer, name, object, userInfo) in - guard let name = name, let observer = observer else { return } - - let connection = unsafeBitCast(observer, to: LocalConnectionHandler.self) - connection.handle(name) -} - -class LocalConnectionHandler: ConnectionHandler -{ - var connectionHandler: ((Connection) -> Void)? - var disconnectionHandler: ((Connection) -> Void)? - - private let dispatchQueue = DispatchQueue(label: "io.altstore.LocalConnectionListener", qos: .utility) - - deinit - { - self.stopListening() - } - - func startListening() - { - let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() - let observer = Unmanaged.passUnretained(self).toOpaque() - - CFNotificationCenterAddObserver(notificationCenter, observer, ReceivedLocalServerConnectionRequest, CFNotificationName.localServerConnectionAvailableRequest.rawValue, nil, .deliverImmediately) - CFNotificationCenterAddObserver(notificationCenter, observer, ReceivedLocalServerConnectionRequest, CFNotificationName.localServerConnectionStartRequest.rawValue, nil, .deliverImmediately) - } - - func stopListening() - { - let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() - let observer = Unmanaged.passUnretained(self).toOpaque() - - CFNotificationCenterRemoveObserver(notificationCenter, observer, .localServerConnectionAvailableRequest, nil) - CFNotificationCenterRemoveObserver(notificationCenter, observer, .localServerConnectionStartRequest, nil) - } - - fileprivate func handle(_ notification: CFNotificationName) - { - switch notification - { - case .localServerConnectionAvailableRequest: - let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() - CFNotificationCenterPostNotification(notificationCenter, .localServerConnectionAvailableResponse, nil, nil, true) - - case .localServerConnectionStartRequest: - let connection = NWConnection(host: "localhost", port: NWEndpoint.Port(rawValue: ALTDeviceListeningSocket)!, using: .tcp) - self.start(connection) - - default: break - } - } -} - -private extension LocalConnectionHandler -{ - func start(_ nwConnection: NWConnection) - { - print("Starting connection to:", nwConnection) - - // Use same instance for all callbacks. - let connection = NetworkConnection(nwConnection) - - nwConnection.stateUpdateHandler = { [weak self] (state) in - switch state - { - case .setup, .preparing: break - - case .ready: - print("Connected to client:", nwConnection.endpoint) - self?.connectionHandler?(connection) - - case .waiting: - print("Waiting for connection...") - - case .failed(let error): - print("Failed to connect to service \(nwConnection.endpoint).", error) - self?.disconnect(connection) - - case .cancelled: - self?.disconnect(connection) - - @unknown default: break - } - } - - nwConnection.start(queue: self.dispatchQueue) - } - - func disconnect(_ connection: Connection) - { - connection.disconnect() - - self.disconnectionHandler?(connection) - } -} diff --git a/AltDaemon/XPCConnectionHandler.swift b/AltDaemon/XPCConnectionHandler.swift new file mode 100644 index 00000000..b6a4b2ef --- /dev/null +++ b/AltDaemon/XPCConnectionHandler.swift @@ -0,0 +1,90 @@ +// +// XPCConnectionHandler.swift +// AltDaemon +// +// Created by Riley Testut on 9/14/20. +// Copyright © 2020 Riley Testut. All rights reserved. +// + +import Foundation +import Security + +class XPCConnectionHandler: NSObject, ConnectionHandler +{ + var connectionHandler: ((Connection) -> Void)? + var disconnectionHandler: ((Connection) -> Void)? + + private let dispatchQueue = DispatchQueue(label: "io.altstore.XPCConnectionListener", qos: .utility) + private let listener = NSXPCListener.makeListener(machServiceName: XPCConnection.machServiceName) + + deinit + { + self.stopListening() + } + + func startListening() + { + self.listener.delegate = self + self.listener.resume() + } + + func stopListening() + { + self.listener.suspend() + } +} + +private extension XPCConnectionHandler +{ + func disconnect(_ connection: Connection) + { + connection.disconnect() + + self.disconnectionHandler?(connection) + } +} + +extension XPCConnectionHandler: NSXPCListenerDelegate +{ + func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool + { + let maximumPathLength = 4 * UInt32(MAXPATHLEN) + + let pathBuffer = UnsafeMutablePointer.allocate(capacity: Int(maximumPathLength)) + defer { pathBuffer.deallocate() } + + proc_pidpath(newConnection.processIdentifier, pathBuffer, maximumPathLength) + + let path = String(cString: pathBuffer) + let fileURL = URL(fileURLWithPath: path) + + var code: UnsafeMutableRawPointer? + defer { code.map { Unmanaged.fromOpaque($0).release() } } + + var status = SecStaticCodeCreateWithPath(fileURL as CFURL, 0, &code) + guard status == 0 else { return false } + + var signingInfo: CFDictionary? + defer { signingInfo.map { Unmanaged.passUnretained($0).release() } } + + status = SecCodeCopySigningInformation(code, kSecCSInternalInformation | kSecCSSigningInformation, &signingInfo) + guard status == 0 else { return false } + + // Only accept connections from AltStore. + guard + let codeSigningInfo = signingInfo as? [String: Any], + let bundleIdentifier = codeSigningInfo["identifier"] as? String, + bundleIdentifier.contains("com.rileytestut.AltStore") + else { return false } + + let connection = XPCConnection(newConnection) + newConnection.invalidationHandler = { [weak self, weak connection] in + guard let self = self, let connection = connection else { return } + self.disconnect(connection) + } + + self.connectionHandler?(connection) + + return true + } +} diff --git a/AltDaemon/package/Library/LaunchDaemons/com.rileytestut.altdaemon.plist b/AltDaemon/package/Library/LaunchDaemons/com.rileytestut.altdaemon.plist index 9ae1f2d0..5b954186 100644 --- a/AltDaemon/package/Library/LaunchDaemons/com.rileytestut.altdaemon.plist +++ b/AltDaemon/package/Library/LaunchDaemons/com.rileytestut.altdaemon.plist @@ -14,8 +14,13 @@ UserName mobile KeepAlive - + RunAtLoad - + + MachServices + + cy:io.altstore.altdaemon + + diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index db88d749..0d935728 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -29,6 +29,8 @@ BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18B0F022E25DF9005C4CF5 /* ToastView.swift */; }; BF18BFFD2485A1E400DD5981 /* WiredConnectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFFC2485A1E400DD5981 /* WiredConnectionHandler.swift */; }; BF1E312B229F474900370A3C /* RequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3129229F474900370A3C /* RequestHandler.swift */; }; + BF1FE358251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1FE357251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift */; }; + BF1FE359251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1FE357251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift */; }; BF29012F2318F6B100D88A45 /* AppBannerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF29012E2318F6B100D88A45 /* AppBannerView.xib */; }; BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2901302318F7A800D88A45 /* AppBannerView.swift */; }; BF340E9A250AD39500A192CB /* ViewApp.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BF989191250AAE86002ACF50 /* ViewApp.intentdefinition */; }; @@ -190,7 +192,7 @@ BF8B17EB250AC40000F8157F /* FileManager+SharedDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */; }; BF8CAE452489E772004D6CCE /* AnisetteDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE422489E772004D6CCE /* AnisetteDataManager.swift */; }; BF8CAE462489E772004D6CCE /* AppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE432489E772004D6CCE /* AppManager.swift */; }; - BF8CAE472489E772004D6CCE /* RequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE442489E772004D6CCE /* RequestHandler.swift */; }; + BF8CAE472489E772004D6CCE /* DaemonRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE442489E772004D6CCE /* DaemonRequestHandler.swift */; }; BF8CAE4C2489F637004D6CCE /* AltDaemon.deb in Resources */ = {isa = PBXBuildFile; fileRef = BF8CAE4A2489F5A0004D6CCE /* AltDaemon.deb */; }; BF8CAE4E248AEABA004D6CCE /* UIDevice+Jailbreak.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE4D248AEABA004D6CCE /* UIDevice+Jailbreak.swift */; }; BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8F69C122E659F700049BA1 /* AppContentViewController.swift */; }; @@ -235,6 +237,9 @@ BFC57A652416C72400EB891E /* DeactivateAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC57A642416C72400EB891E /* DeactivateAppOperation.swift */; }; BFC57A6E2416FC5D00EB891E /* InstalledAppsCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC57A6D2416FC5D00EB891E /* InstalledAppsCollectionHeaderView.swift */; }; BFC57A702416FC7600EB891E /* InstalledAppsCollectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFC57A6F2416FC7600EB891E /* InstalledAppsCollectionHeaderView.xib */; }; + BFC712C32512D5F100AB5EBE /* XPCConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC712C12512D5F100AB5EBE /* XPCConnection.swift */; }; + BFC712C42512D5F100AB5EBE /* XPCConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC712C12512D5F100AB5EBE /* XPCConnection.swift */; }; + BFC712C52512D5F100AB5EBE /* XPCConnectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC712C22512D5F100AB5EBE /* XPCConnectionHandler.swift */; }; BFC84A4D2421A19100853474 /* SourcesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC84A4C2421A19100853474 /* SourcesViewController.swift */; }; BFCB9207250AB2120057B44E /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BFCB9206250AB2120057B44E /* Colors.xcassets */; }; BFCCB51A245E3401001853EA /* VerifyAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCCB519245E3401001853EA /* VerifyAppOperation.swift */; }; @@ -307,7 +312,6 @@ BFECAC8924FD950E0077C41F /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF22485828200DD5981 /* ConnectionManager.swift */; }; BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */; }; BFECAC8B24FD950E0077C41F /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; }; - BFECAC8C24FD950E0077C41F /* NetworkConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CD2489ABE90097E58C /* NetworkConnection.swift */; }; BFECAC8D24FD950E0077C41F /* ALTConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BD723C93DB700A89F2D /* ALTConstants.m */; }; BFECAC8E24FD950E0077C41F /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF624858BDE00DD5981 /* Connection.swift */; }; BFECAC8F24FD950E0077C41F /* Result+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBAC8852295C90300587369 /* Result+Conveniences.swift */; }; @@ -328,7 +332,6 @@ BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B6992322D7D0007A79E1 /* UIScreen+CompactHeight.swift */; }; BFF615A82510042B00484D3B /* AltStoreCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */; }; BFF767C82489A74E0097E58C /* WirelessConnectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767C72489A74E0097E58C /* WirelessConnectionHandler.swift */; }; - BFFCFA582488648D0077BFCE /* LocalConnectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF10EB3124870B3F0055E6DB /* LocalConnectionHandler.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -432,7 +435,6 @@ BF088D322501A4FF008082D9 /* OpenSSL.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OpenSSL.xcframework; path = Dependencies/AltSign/Dependencies/OpenSSL/Frameworks/OpenSSL.xcframework; sourceTree = ""; }; BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppManager.swift; sourceTree = ""; }; BF0DCA652433BDF500E3A595 /* AnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsManager.swift; sourceTree = ""; }; - BF10EB3124870B3F0055E6DB /* LocalConnectionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalConnectionHandler.swift; sourceTree = ""; }; BF10EB33248730750055E6DB /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; BF18B0F022E25DF9005C4CF5 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; BF18BFE724857D7900DD5981 /* AltDaemon */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = AltDaemon; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -447,6 +449,7 @@ BF1E314722A060F300370A3C /* AltStore-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AltStore-Bridging-Header.h"; sourceTree = ""; }; BF1E314822A060F400370A3C /* NSError+ALTServerError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSError+ALTServerError.h"; sourceTree = ""; }; BF1E314922A060F400370A3C /* NSError+ALTServerError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSError+ALTServerError.m"; sourceTree = ""; }; + BF1FE357251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSXPCConnection+MachServices.swift"; sourceTree = ""; }; BF219A7E22CAC431007676A6 /* AltStore.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltStore.entitlements; sourceTree = ""; }; BF29012E2318F6B100D88A45 /* AppBannerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AppBannerView.xib; sourceTree = ""; }; BF2901302318F7A800D88A45 /* AppBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppBannerView.swift; sourceTree = ""; }; @@ -630,7 +633,7 @@ BF8B17F0250AC62400F8157F /* AltWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltWidgetExtension.entitlements; sourceTree = ""; }; BF8CAE422489E772004D6CCE /* AnisetteDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnisetteDataManager.swift; sourceTree = ""; }; BF8CAE432489E772004D6CCE /* AppManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppManager.swift; sourceTree = ""; }; - BF8CAE442489E772004D6CCE /* RequestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestHandler.swift; sourceTree = ""; }; + BF8CAE442489E772004D6CCE /* DaemonRequestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DaemonRequestHandler.swift; sourceTree = ""; }; BF8CAE4A2489F5A0004D6CCE /* AltDaemon.deb */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = AltDaemon.deb; sourceTree = ""; }; BF8CAE4D248AEABA004D6CCE /* UIDevice+Jailbreak.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Jailbreak.swift"; sourceTree = ""; }; BF8F69C122E659F700049BA1 /* AppContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentViewController.swift; sourceTree = ""; }; @@ -662,6 +665,8 @@ BFC57A642416C72400EB891E /* DeactivateAppOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeactivateAppOperation.swift; sourceTree = ""; }; BFC57A6D2416FC5D00EB891E /* InstalledAppsCollectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstalledAppsCollectionHeaderView.swift; sourceTree = ""; }; BFC57A6F2416FC7600EB891E /* InstalledAppsCollectionHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstalledAppsCollectionHeaderView.xib; sourceTree = ""; }; + BFC712C12512D5F100AB5EBE /* XPCConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XPCConnection.swift; sourceTree = ""; }; + BFC712C22512D5F100AB5EBE /* XPCConnectionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XPCConnectionHandler.swift; sourceTree = ""; }; BFC84A4C2421A19100853474 /* SourcesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourcesViewController.swift; sourceTree = ""; }; BFCB9206250AB2120057B44E /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; BFCCB519245E3401001853EA /* VerifyAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyAppOperation.swift; sourceTree = ""; }; @@ -858,8 +863,8 @@ children = ( BFFCFA45248835530077BFCE /* AltDaemon.entitlements */, BF10EB33248730750055E6DB /* main.swift */, - BF8CAE442489E772004D6CCE /* RequestHandler.swift */, - BF10EB3124870B3F0055E6DB /* LocalConnectionHandler.swift */, + BFC712C22512D5F100AB5EBE /* XPCConnectionHandler.swift */, + BF8CAE442489E772004D6CCE /* DaemonRequestHandler.swift */, BF8CAE422489E772004D6CCE /* AnisetteDataManager.swift */, BF8CAE432489E772004D6CCE /* AppManager.swift */, BF18C0032485B4DE00DD5981 /* AltDaemon-Bridging-Header.h */, @@ -1580,6 +1585,7 @@ BFBAC8852295C90300587369 /* Result+Conveniences.swift */, BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */, BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */, + BF1FE357251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift */, ); path = Extensions; sourceTree = ""; @@ -1602,6 +1608,7 @@ BF18BFFE2485A42800DD5981 /* ALTConnection.h */, BF18BFF624858BDE00DD5981 /* Connection.swift */, BFF767CD2489ABE90097E58C /* NetworkConnection.swift */, + BFC712C12512D5F100AB5EBE /* XPCConnection.swift */, ); path = Connections; sourceTree = ""; @@ -2126,10 +2133,12 @@ buildActionMask = 2147483647; files = ( BF8CAE452489E772004D6CCE /* AnisetteDataManager.swift in Sources */, + BF1FE358251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */, BFECAC8F24FD950E0077C41F /* Result+Conveniences.swift in Sources */, - BF8CAE472489E772004D6CCE /* RequestHandler.swift in Sources */, - BFECAC8C24FD950E0077C41F /* NetworkConnection.swift in Sources */, + BF8CAE472489E772004D6CCE /* DaemonRequestHandler.swift in Sources */, BFECAC8824FD950E0077C41F /* CodableServerError.swift in Sources */, + BFC712C32512D5F100AB5EBE /* XPCConnection.swift in Sources */, + BFC712C52512D5F100AB5EBE /* XPCConnectionHandler.swift in Sources */, BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */, BFECAC8D24FD950E0077C41F /* ALTConstants.m in Sources */, BFECAC8924FD950E0077C41F /* ConnectionManager.swift in Sources */, @@ -2138,7 +2147,6 @@ BFECAC8B24FD950E0077C41F /* ServerProtocol.swift in Sources */, BFECAC9624FD98BB0077C41F /* NSError+ALTServerError.m in Sources */, BF10EB34248730750055E6DB /* main.swift in Sources */, - BFFCFA582488648D0077BFCE /* LocalConnectionHandler.swift in Sources */, BF8CAE462489E772004D6CCE /* AppManager.swift in Sources */, BFECAC9024FD950E0077C41F /* Bundle+AltStore.swift in Sources */, ); @@ -2271,6 +2279,7 @@ BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */, BFAECC572501B0A400528F27 /* ConnectionManager.swift in Sources */, BF66EE9D2501AEC1007EE018 /* AppProtocol.swift in Sources */, + BFC712C42512D5F100AB5EBE /* XPCConnection.swift in Sources */, BF66EE8C2501AEB2007EE018 /* Keychain.swift in Sources */, BF66EED42501AECA007EE018 /* AltStore5ToAltStore6.xcmappingmodel in Sources */, BF66EE972501AEBC007EE018 /* ALTAppPermission.m in Sources */, @@ -2303,6 +2312,7 @@ BF66EECC2501AECA007EE018 /* Source.swift in Sources */, BF66EED72501AECA007EE018 /* InstalledApp.swift in Sources */, BF66EECE2501AECA007EE018 /* InstalledAppPolicy.swift in Sources */, + BF1FE359251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */, BF66EEA62501AEC5007EE018 /* PatreonAPI.swift in Sources */, BF66EED02501AECA007EE018 /* AltStore6ToAltStore7.xcmappingmodel in Sources */, BF66EEDC2501AECA007EE018 /* MergePolicy.swift in Sources */, diff --git a/AltStore/Operations/FindServerOperation.swift b/AltStore/Operations/FindServerOperation.swift index de8aa23f..12654eeb 100644 --- a/AltStore/Operations/FindServerOperation.swift +++ b/AltStore/Operations/FindServerOperation.swift @@ -8,6 +8,7 @@ import Foundation +import AltStoreCore import Roxas private let ReceivedServerConnectionResponse: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void = @@ -52,11 +53,11 @@ class FindServerOperation: ResultOperation // Prepare observers to receive callback from wired connection or background daemon (if available). CFNotificationCenterAddObserver(notificationCenter, observer, ReceivedServerConnectionResponse, CFNotificationName.wiredServerConnectionAvailableResponse.rawValue, nil, .deliverImmediately) - CFNotificationCenterAddObserver(notificationCenter, observer, ReceivedServerConnectionResponse, CFNotificationName.localServerConnectionAvailableResponse.rawValue, nil, .deliverImmediately) // Post notifications. CFNotificationCenterPostNotification(notificationCenter, .wiredServerConnectionAvailableRequest, nil, nil, true) - CFNotificationCenterPostNotification(notificationCenter, .localServerConnectionAvailableRequest, nil, nil, true) + + self.discoverLocalServer() // Wait for either callback or timeout. DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) { @@ -97,18 +98,28 @@ class FindServerOperation: ResultOperation let observer = Unmanaged.passUnretained(self).toOpaque() CFNotificationCenterRemoveObserver(notificationCenter, observer, .wiredServerConnectionAvailableResponse, nil) - CFNotificationCenterRemoveObserver(notificationCenter, observer, .localServerConnectionAvailableResponse, nil) } } fileprivate extension FindServerOperation { + func discoverLocalServer() + { + let connection = XPCConnection() + connection.connect { (result) in + switch result + { + case .failure(let error): print("Could not connect to AltDaemon XPC service.", error) + case .success: self.isLocalServerConnectionAvailable = true + } + } + } + func handle(_ notification: CFNotificationName) { switch notification { case .wiredServerConnectionAvailableResponse: self.isWiredServerConnectionAvailable = true - case .localServerConnectionAvailableResponse: self.isLocalServerConnectionAvailable = true default: break } } diff --git a/AltStore/Server/ServerConnection.swift b/AltStore/Server/ServerConnection.swift index 0979af8b..e663a2e0 100644 --- a/AltStore/Server/ServerConnection.swift +++ b/AltStore/Server/ServerConnection.swift @@ -14,9 +14,9 @@ import AltStoreCore class ServerConnection { var server: Server - var connection: NWConnection + var connection: Connection - init(server: Server, connection: NWConnection) + init(server: Server, connection: Connection) { self.server = server self.connection = connection @@ -37,17 +37,15 @@ class ServerConnection data = try JSONEncoder().encode(payload) } - func process(_ error: Error?) -> Bool + func process(_ result: Result) -> Bool { - if error != nil + switch result { - completionHandler(.failure(ConnectionError.connectionDropped)) + case .success: return true + case .failure(let error): + completionHandler(.failure(error)) return false } - else - { - return true - } } if prependSize @@ -55,22 +53,21 @@ class ServerConnection let requestSize = Int32(data.count) let requestSizeData = withUnsafeBytes(of: requestSize) { Data($0) } - self.connection.send(content: requestSizeData, completion: .contentProcessed { (error) in - guard process(error) else { return } + self.connection.send(requestSizeData) { (result) in + guard process(result) else { return } - self.connection.send(content: data, completion: .contentProcessed { (error) in - guard process(error) else { return } + self.connection.send(data) { (result) in + guard process(result) else { return } completionHandler(.success(())) - }) - }) - + } + } } else { - connection.send(content: data, completion: .contentProcessed { (error) in - guard process(error) else { return } + self.connection.send(data) { (result) in + guard process(result) else { return } completionHandler(.success(())) - }) + } } } catch @@ -84,16 +81,16 @@ class ServerConnection { let size = MemoryLayout.size - self.connection.receive(minimumIncompleteLength: size, maximumLength: size) { (data, _, _, error) in + self.connection.receiveData(expectedSize: size) { (result) in do { - let data = try self.process(data: data, error: error) + let data = try result.get() let expectedBytes = Int(data.withUnsafeBytes { $0.load(as: Int32.self) }) - self.connection.receive(minimumIncompleteLength: expectedBytes, maximumLength: expectedBytes) { (data, _, _, error) in + self.connection.receiveData(expectedSize: expectedBytes) { (result) in do { - let data = try self.process(data: data, error: error) + let data = try result.get() let response = try AltStoreCore.JSONDecoder().decode(ServerResponse.self, from: data) completionHandler(.success(response)) @@ -111,36 +108,3 @@ class ServerConnection } } } - -private extension ServerConnection -{ - func process(data: Data?, error: NWError?) throws -> Data - { - do - { - do - { - guard let data = data else { throw error ?? ALTServerError(.unknown) } - return data - } - catch let error as NWError - { - print("Error receiving data from connection \(connection)", error) - - throw ALTServerError(.lostConnection) - } - catch - { - throw error - } - } - catch let error as ALTServerError - { - throw error - } - catch - { - preconditionFailure("A non-ALTServerError should never be thrown from this method.") - } - } -} diff --git a/AltStore/Server/ServerManager.swift b/AltStore/Server/ServerManager.swift index 0d807aba..91b76991 100644 --- a/AltStore/Server/ServerManager.swift +++ b/AltStore/Server/ServerManager.swift @@ -63,39 +63,23 @@ extension ServerManager func connect(to server: Server, completion: @escaping (Result) -> Void) { DispatchQueue.global().async { - func finish(_ result: Result) + func finish(_ result: Result) { - completion(result) - } - - func start(_ connection: NWConnection) - { - connection.stateUpdateHandler = { [unowned connection] (state) in - switch state - { - case .failed(let error): - print("Failed to connect to service \(server.service?.name ?? "").", error) - finish(.failure(ConnectionError.connectionFailed)) - - case .cancelled: - finish(.failure(OperationError.cancelled)) - - case .ready: - let connection = ServerConnection(server: server, connection: connection) - finish(.success(connection)) - - case .waiting: break - case .setup: break - case .preparing: break - @unknown default: break - } + switch result + { + case .failure(let error): completion(.failure(error)) + case .success(let connection): + let serverConnection = ServerConnection(server: server, connection: connection) + completion(.success(serverConnection)) } - - connection.start(queue: self.dispatchQueue) } - if let incomingConnectionsSemaphore = self.incomingConnectionsSemaphore, server.connectionType != .wireless + switch server.connectionType { + case .local: self.connectToLocalServer(completion: finish(_:)) + case .wired: + guard let incomingConnectionsSemaphore = self.incomingConnectionsSemaphore else { return finish(.failure(ALTServerError(.connectionFailed))) } + print("Waiting for incoming connection...") let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() @@ -103,39 +87,27 @@ extension ServerManager switch server.connectionType { case .wired: CFNotificationCenterPostNotification(notificationCenter, .wiredServerConnectionStartRequest, nil, nil, true) - case .local: CFNotificationCenterPostNotification(notificationCenter, .localServerConnectionStartRequest, nil, nil, true) - case .wireless: break + case .local, .wireless: break } _ = incomingConnectionsSemaphore.wait(timeout: .now() + 10.0) if let connection = self.incomingConnections?.popLast() { - start(connection) + self.connectToRemoteServer(server, connection: connection, completion: finish(_:)) } else { finish(.failure(ALTServerError(.connectionFailed))) } - } - else if let service = server.service - { + + case .wireless: + guard let service = server.service else { return finish(.failure(ALTServerError(.connectionFailed))) } + print("Connecting to service:", service) - let parameters = NWParameters.tcp - - if server.connectionType == .local - { - // Prevent AltStore from initiating connections over multiple interfaces simultaneously 🤷‍♂️ - parameters.requiredInterfaceType = .loopback - } - - let connection = NWConnection(to: .service(name: service.name, type: service.type, domain: service.domain, interface: nil), using: parameters) - start(connection) - } - else - { - finish(.failure(ALTServerError(.connectionFailed))) + let connection = NWConnection(to: .service(name: service.name, type: service.type, domain: service.domain, interface: nil), using: .tcp) + self.connectToRemoteServer(server, connection: connection, completion: finish(_:)) } } } @@ -191,6 +163,47 @@ private extension ServerManager self.incomingConnections = nil self.incomingConnectionsSemaphore = nil } + + func connectToRemoteServer(_ server: Server, connection: NWConnection, completion: @escaping (Result) -> Void) + { + connection.stateUpdateHandler = { [unowned connection] (state) in + switch state + { + case .failed(let error): + print("Failed to connect to service \(server.service?.name ?? "").", error) + completion(.failure(ConnectionError.connectionFailed)) + + case .cancelled: + completion(.failure(OperationError.cancelled)) + + case .ready: + let connection = NetworkConnection(connection) + completion(.success(connection)) + + case .waiting: break + case .setup: break + case .preparing: break + @unknown default: break + } + } + + connection.start(queue: self.dispatchQueue) + } + + func connectToLocalServer(completion: @escaping (Result) -> Void) + { + let connection = XPCConnection() + connection.connect { (result) in + switch result + { + case .failure(let error): + print("Could not connect to AltDaemon XPC service.", error) + completion(.failure(error)) + + case .success: completion(.success(connection)) + } + } + } } extension ServerManager: NetServiceBrowserDelegate diff --git a/Dependencies/AltSign b/Dependencies/AltSign index 508abc4c..229e7d57 160000 --- a/Dependencies/AltSign +++ b/Dependencies/AltSign @@ -1 +1 @@ -Subproject commit 508abc4cdfe32c6f6708f88064ad16569dfa1de7 +Subproject commit 229e7d5751d9b56c284b283e64ede9b1468f6dc1 diff --git a/Shared/Categories/CFNotificationName+AltStore.h b/Shared/Categories/CFNotificationName+AltStore.h index 634ba563..01c2a336 100644 --- a/Shared/Categories/CFNotificationName+AltStore.h +++ b/Shared/Categories/CFNotificationName+AltStore.h @@ -14,8 +14,4 @@ extern CFNotificationName const ALTWiredServerConnectionAvailableRequest NS_SWIF extern CFNotificationName const ALTWiredServerConnectionAvailableResponse NS_SWIFT_NAME(wiredServerConnectionAvailableResponse); extern CFNotificationName const ALTWiredServerConnectionStartRequest NS_SWIFT_NAME(wiredServerConnectionStartRequest); -extern CFNotificationName const ALTLocalServerConnectionAvailableRequest NS_SWIFT_NAME(localServerConnectionAvailableRequest); -extern CFNotificationName const ALTLocalServerConnectionAvailableResponse NS_SWIFT_NAME(localServerConnectionAvailableResponse); -extern CFNotificationName const ALTLocalServerConnectionStartRequest NS_SWIFT_NAME(localServerConnectionStartRequest); - NS_ASSUME_NONNULL_END diff --git a/Shared/Categories/CFNotificationName+AltStore.m b/Shared/Categories/CFNotificationName+AltStore.m index c74c55a1..ac0eb5d1 100644 --- a/Shared/Categories/CFNotificationName+AltStore.m +++ b/Shared/Categories/CFNotificationName+AltStore.m @@ -11,7 +11,3 @@ CFNotificationName const ALTWiredServerConnectionAvailableRequest = CFSTR("io.altstore.Request.WiredServerConnectionAvailable"); CFNotificationName const ALTWiredServerConnectionAvailableResponse = CFSTR("io.altstore.Response.WiredServerConnectionAvailable"); CFNotificationName const ALTWiredServerConnectionStartRequest = CFSTR("io.altstore.Request.WiredServerConnectionStart"); - -CFNotificationName const ALTLocalServerConnectionAvailableRequest = CFSTR("io.altstore.Request.LocalServerConnectionAvailable"); -CFNotificationName const ALTLocalServerConnectionAvailableResponse = CFSTR("io.altstore.Response.LocalServerConnectionAvailable"); -CFNotificationName const ALTLocalServerConnectionStartRequest = CFSTR("io.altstore.Request.LocalServerConnectionStart"); diff --git a/Shared/Connections/XPCConnection.swift b/Shared/Connections/XPCConnection.swift new file mode 100644 index 00000000..eb044f16 --- /dev/null +++ b/Shared/Connections/XPCConnection.swift @@ -0,0 +1,150 @@ +// +// XPCConnection.swift +// AltKit +// +// Created by Riley Testut on 6/15/20. +// Copyright © 2020 Riley Testut. All rights reserved. +// + +import Foundation + +@objc private protocol XPCConnectionProxy +{ + func ping(completionHandler: @escaping () -> Void) + func receive(_ data: Data, completionHandler: @escaping (Bool, Error?) -> Void) +} + +extension XPCConnection +{ + public static let machServiceName = "cy:io.altstore.altdaemon" +} + +public class XPCConnection: NSObject, Connection +{ + public let xpcConnection: NSXPCConnection + + private let queue = DispatchQueue(label: "io.altstore.XPCConnection") + private let dispatchGroup = DispatchGroup() + private var semaphore: DispatchSemaphore? + private var buffer = Data(capacity: 1024) + + private var error: Error? + + public init(_ xpcConnection: NSXPCConnection = .makeConnection(machServiceName: XPCConnection.machServiceName)) + { + let proxyInterface = NSXPCInterface(with: XPCConnectionProxy.self) + xpcConnection.remoteObjectInterface = proxyInterface + xpcConnection.exportedInterface = proxyInterface + + self.xpcConnection = xpcConnection + + super.init() + + xpcConnection.interruptionHandler = { + self.error = ALTServerError(.lostConnection) + } + + xpcConnection.exportedObject = self + xpcConnection.resume() + } + + deinit + { + self.disconnect() + } +} + +private extension XPCConnection +{ + func makeProxy(errorHandler: @escaping (Error) -> Void) -> XPCConnectionProxy + { + let proxy = self.xpcConnection.remoteObjectProxyWithErrorHandler { (error) in + print("Error messaging remote object proxy:", error) + self.error = error + errorHandler(error) + } as! XPCConnectionProxy + + return proxy + } +} + +public extension XPCConnection +{ + func connect(completionHandler: @escaping (Result) -> Void) + { + let proxy = self.makeProxy { (error) in + completionHandler(.failure(error)) + } + + proxy.ping { + completionHandler(.success(())) + } + } + + func disconnect() + { + self.xpcConnection.invalidate() + } + + func __send(_ data: Data, completionHandler: @escaping (Bool, Error?) -> Void) + { + guard self.error == nil else { return completionHandler(false, self.error) } + + let proxy = self.makeProxy { (error) in + completionHandler(false, error) + } + + proxy.receive(data) { (success, error) in + completionHandler(success, error) + } + } + + func __receiveData(expectedSize: Int, completionHandler: @escaping (Data?, Error?) -> Void) + { + guard self.error == nil else { return completionHandler(nil, self.error) } + + self.queue.async { + let copiedBuffer = self.buffer // Copy buffer to prevent runtime crashes. + guard copiedBuffer.count >= expectedSize else { + self.semaphore = DispatchSemaphore(value: 0) + DispatchQueue.global().async { + _ = self.semaphore?.wait(timeout: .now() + 1.0) + self.__receiveData(expectedSize: expectedSize, completionHandler: completionHandler) + } + return + } + + let data = copiedBuffer.prefix(expectedSize) + self.buffer = copiedBuffer.dropFirst(expectedSize) + + completionHandler(data, nil) + } + } +} + +extension XPCConnection +{ + override public var description: String { + return "\(self.xpcConnection.endpoint) (XPC)" + } +} + +extension XPCConnection: XPCConnectionProxy +{ + fileprivate func ping(completionHandler: @escaping () -> Void) + { + completionHandler() + } + + fileprivate func receive(_ data: Data, completionHandler: @escaping (Bool, Error?) -> Void) + { + self.queue.async { + self.buffer.append(data) + + self.semaphore?.signal() + self.semaphore = nil + + completionHandler(true, nil) + } + } +} diff --git a/Shared/Extensions/NSXPCConnection+MachServices.swift b/Shared/Extensions/NSXPCConnection+MachServices.swift new file mode 100644 index 00000000..711e8deb --- /dev/null +++ b/Shared/Extensions/NSXPCConnection+MachServices.swift @@ -0,0 +1,33 @@ +// +// NSXPCConnection+MachServices.swift +// AltStore +// +// Created by Riley Testut on 9/22/20. +// Copyright © 2020 Riley Testut. All rights reserved. +// + +import Foundation + +@objc private protocol XPCPrivateAPI +{ + init(machServiceName: String) + init(machServiceName: String, options: NSXPCConnection.Options) +} + +public extension NSXPCConnection +{ + class func makeConnection(machServiceName: String) -> NSXPCConnection + { + let connection = unsafeBitCast(self, to: XPCPrivateAPI.Type.self).init(machServiceName: machServiceName, options: .privileged) + return unsafeBitCast(connection, to: NSXPCConnection.self) + } +} + +public extension NSXPCListener +{ + class func makeListener(machServiceName: String) -> NSXPCListener + { + let listener = unsafeBitCast(self, to: XPCPrivateAPI.Type.self).init(machServiceName: machServiceName) + return unsafeBitCast(listener, to: NSXPCListener.self) + } +}